package cn.edu.thu.tsquality.core.algorithm.variance.constraints;

import cn.edu.thu.tsquality.core.common.algorithm.IAlgorithm;
import cn.edu.thu.tsquality.core.common.table.Header;
import cn.edu.thu.tsquality.core.common.table.Row;
import cn.edu.thu.tsquality.core.common.table.Table;
import scala.Serializable;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/*
方差约束检测不精确的数据：
一段时间内的数据方差应满足约束，否则认为存在不精确的数据
对于每一个时刻的数据，检查包含此时刻的窗口中，有多少窗口的方差是异常的。如果超过了置信度，那么认为这个时刻的数据是不精确的。

当计算的窗口长度小于指定的窗口长度时，要使用更小的方差约束。
例如，i=3, windowSize = 5, 包含i=3数据的所有的窗口为[0,1,2,3] [0,1,2,3,4] [1,2,3,4,5] [2,3,4,5,6] [3,4,5,6,7]
[0,1,2,3]这个窗口内只有4个数据，需要使用更小的方差约束。
指定的方差约束constraint是5个数据的方差。

我们使用以下方法计算其他个数的数据的方差约束：
假设数据均匀增加，那么5个数据y1<y2<y3<y4<y5，y5-y4 = y4-y3 = y3-y2 = y2-y1 = averageGap。
其方差达到方差约束，可以求得averageGap。
对于4个数据，可以假设数据为y1<y2<y3<y4, y4-y3 = y3-y2 = y2-y1 = averageGap 计算方差，作为窗口为4时的方差约束

于是，我们可以指定averageGap计算方差约束，也可以指定constraint计算方差约束。

*/

public class VarianceConstraints implements IAlgorithm, Serializable
{
    private String attr;
    private Integer windowSize;
    private ArrayList<Double> varianceConstraint = new ArrayList<>();
    private Integer confidence;

    VarianceConstraints(String attr, Integer windowSize, Integer confidence, Double constraint, boolean isDifference)
    {
        this.attr = attr;
        this.windowSize = windowSize;
        this.confidence = confidence;

        double averageGap;

        double windowMean = 1.0 * (windowSize - 1) / 2;
        double varianceBase = 0.0;
        if(isDifference)
            averageGap = constraint;
        else
        {
            for (int i = 0; i < windowSize; i++)
                varianceBase += (i - windowMean) * (i - windowMean);
            varianceBase /= windowSize;
            averageGap = Math.sqrt(constraint / varianceBase);
        }

        // 计算不同长度窗口的方差约束
        varianceConstraint.add(0.0);
        for(int i = 1; i <= windowSize; i++)
        {
            windowMean = (i - 1) / 2;
            varianceBase = 0.0;
            for(int j = 0; j < i; j++)
                varianceBase += (j - windowMean) * (j - windowMean);
            varianceBase /= i;
            //this.varianceConstraint.put(i, varianceBase * averageGap * averageGap);
            this.varianceConstraint.add(varianceBase * averageGap * averageGap);
        }
        // println(this.varianceConstraint.toString());
    }

    private void print(String str)
    {
        System.out.print(str);
    }

    private void println(String str)
    {
        System.out.println(str);
    }


    public Table run(Table table)
    {

        ArrayList<String> tableRows = new ArrayList<>();
        List tableAttrList = Arrays.asList(table.getHeader().toArray());
        if(!tableAttrList.contains(attr))
            throw new IllegalArgumentException("there is no column named " + attr);
        List<Row> rows = table.getRows();
        for(Row row : rows)
            tableRows.add((String)row.get(attr));
        int inputDataSize = tableRows.size();

        Double[] attrData = new Double[inputDataSize];
        for(int i = 0; i < inputDataSize; i++)
            if(tableRows.get(i).equals(""))
                attrData[i] = 0.0;
            else
                attrData[i] = Double.parseDouble(tableRows.get(i));

        //----------------
//        println(attr+" [" + windowSize + "," + varianceConstraint.toString()+"]");

//        println(inputDataSize+"");
//        print("Interval = ");
//        for(Double i : attrData)
//            print(i+" ");
//        println("");
        //-----------------

        // 定义返回数据。
        // 返回table为两列，第一列为源数据，第二列为是否需要修复的标记。
        // 返回数据作为修复算法的输入

        String flagAttr = "needRepair_[" + windowSize + "," + confidence + "]";
        Header returnHeader = new Header(new String[] {attr, flagAttr});
        List<Row> returnRows = new ArrayList<>();

        for (int index = 0; index < inputDataSize; index++)
        {
            int realWindows = 0;
            int errWindows = 0;

            for (int left = index - windowSize + 1; left <= index; left++)
            {
                int right = left + windowSize - 1;
                int windowLeft = Math.max(0, left);
                int windowRight = Math.min(inputDataSize - 1, right);

                int realWindowLength = windowRight - windowLeft + 1;

                // 计算方差
                double mean = 0;
                double variance = 0;

                for (int i = windowLeft; i <= windowRight; i++)
                    mean += attrData[i];
                mean /= realWindowLength;

                for (int i = windowLeft; i <= windowRight; i++)
                    variance += (attrData[i]-mean)*(attrData[i]-mean);
                variance/=realWindowLength;

                realWindows ++;
                if(variance>varianceConstraint.get(realWindowLength))
                    errWindows++;
            }

            // 异常窗口所占比例是否超过置信度
            double limit = 1.0 * confidence / windowSize * realWindows;
            if(errWindows >= limit)
                returnRows.add(new Row(returnHeader, new String[]{tableRows.get(index), "1"}, index));
            else
                returnRows.add(new Row(returnHeader, new String[]{tableRows.get(index), "0"}, index));
        }

        return new Table(returnHeader, returnRows);
    }
}